/**
 * Copyright 2010 Jake Jackson
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jake.nb.ext.ant.propdisplay;

import java.util.EnumSet;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import org.jake.nb.ext.ant.parser.AntParser;
import org.netbeans.lib.editor.hyperlink.spi.HyperlinkProviderExt;
import org.netbeans.lib.editor.hyperlink.spi.HyperlinkType;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;

/**
 * A class to display resolved properties. Hyperlink types are supported the follwing way:
 * GO_TO_DECLARATION = displays the resolved property directly under the pointer and tries to go to declaration on click
 * ALT_HYPERLINK = shows the resolved line the property under the pointer is in.
 *
 * Example:
 * ${property1}/someString---${property2}
 * where: ${property1} = Foo and ${property2} = bar
 *
 * Pointing on ${property1} with Ctrl only clicked will result in a tooltip showing: "Foo", clicking on it will go to
 * the declaration of property1.
 *
 * Pointing on ${property1} with Ctrl + Alt held will result in a tooltip showing
 * <pre>
 *   Foo/someString---bar
 *   ${property1} -> Foo
 *   ${property2} -> bar
 * </pre>
 *
 * @author JakeJ
 */
public class ResolvedPropertyDisplayer implements HyperlinkProviderExt {

  @Override
  public Set<HyperlinkType> getSupportedHyperlinkTypes() {
    return EnumSet.of(HyperlinkType.GO_TO_DECLARATION, HyperlinkType.ALT_HYPERLINK);
  }

  @Override
  public boolean isHyperlinkPoint(final Document doc, final int offset, final HyperlinkType type) {
    return getHyperlinkSpan(doc, offset, type) != null;
  }

  @Override
  public int[] getHyperlinkSpan(final Document doc, final int offset, final HyperlinkType type) {
    int[] ret = null;
    // use a if or switch statement, as the type can still be null - the surrounding framework doesn't state
    // it could not happen, so be on the safe side
    if (HyperlinkType.ALT_HYPERLINK == type) {
      ret = getAltSpan(doc, offset);
    } else if (HyperlinkType.GO_TO_DECLARATION == type) {
      ret = getGotoSpan(doc, offset);
    }
    return ret;
  }

  /**
   * Gets the span for the GO_TO_DECLARATION span.
   * @param doc the document to get it for
   * @param offset the offset the mouse points too
   * @return the span starting with "$" ending with "}" or null, if there's no such span near the offset
   */
  private int[] getGotoSpan(final Document doc, final int offset) {
    int[] ret = null;
    // seek for the starting "${"
    int start = -1;
    int end = -1;
    for (int i = offset; i > -1; i--) { // this is quite inefficient - we have to find a better way - may decrement by 2 and checke the next four chars?
      try {
        start = doc.getText(i, 2).indexOf("${"); // NOI18N
        if (start > -1 && start <= offset) {
          start = i;
          end = start + doc.getText(start, doc.getLength() - start).indexOf("}") + 1; // NOI18N
          break;
        }
      } catch (BadLocationException ex) {
        Exceptions.printStackTrace(ex);
      }
    }

    if (start > -1 && end > -1 && start <= offset && end >= offset) {
      ret = new int[]{start, end};
    }

    return ret;
  }

  private int[] getAltSpan(final Document doc, final int offset) {
    int[] ret = null;
    int startIndex = -1;
    int endIndex = -1;
    try {
      String text = doc.getText(offset, doc.getLength() - offset);
      int closing = text.indexOf('"'); // NOI18N
      if (closing > -1) {
        endIndex = offset + closing;
      }

      for (int i = offset; i > -1; i--) {
        if ("\"".equals(doc.getText(i, 1))) { // NOI18N
          startIndex = i + 1;
          break;
        }
      }

      if (startIndex < endIndex) {

        text = doc.getText(startIndex, endIndex - startIndex);

        // we want to resolve properties only, so at "${...}" must be present
        if (!(text.contains("$") && text.contains("{") && text.contains("}"))) { // NOI18N
          startIndex = -1;
          endIndex = -1;
        }
      }

    } catch (BadLocationException ex) {
      Logger.getLogger(ResolvedPropertyDisplayer.class.getName()).log(Level.SEVERE, null, ex);
    }

    if (startIndex > -1 && endIndex > -1 && startIndex <= offset && endIndex >= offset) {
      ret = new int[]{startIndex, endIndex};
    }

    return ret;
  }

  @Override
  public void performClickAction(final Document doc, final int offset, final HyperlinkType type) {
    if (HyperlinkType.GO_TO_DECLARATION == type) {
      final int[] hyperlinkSpan = getHyperlinkSpan(doc, offset, type);
      if (hyperlinkSpan != null) {
        GotoPropertyDeclaration.gotoDeclaration(doc, hyperlinkSpan);
      }
    }
  }

  @Override
  public String getTooltipText(final Document doc, final int offset, final HyperlinkType type) {
    String retVal = ""; // NOI18N
    String origText = ""; // NOI18N
    final int[] hyperlinkSpan = getHyperlinkSpan(doc, offset, type);
    if (hyperlinkSpan != null) {
      try {
        origText = doc.getText(hyperlinkSpan[0], hyperlinkSpan[1] - hyperlinkSpan[0]);
        retVal = AntParser.resolveProperties(doc, origText);
      } catch (BadLocationException ex) {
        Logger.getLogger(ResolvedPropertyDisplayer.class.getName()).log(Level.SEVERE, null, ex);
      }

    }

    if (retVal.equals(origText)) {
      retVal = NbBundle.getMessage(this.getClass(), "Hint_CannotResolveProperty"); // NOI18N
    } else {
      if (HyperlinkType.ALT_HYPERLINK == type) {
        final StringBuilder builder = new StringBuilder("<html>"); // NOI18N
        builder.append("<b>").append(retVal).append("</b><p>"); // NOI18N
        final StringTokenizer tokenizer = new StringTokenizer(origText, "$"); // NOI18N
        while (tokenizer.hasMoreTokens()) {
          String nextToken = tokenizer.nextToken();
          if (nextToken.contains("{") && nextToken.contains("}")) { // NOI18N
            nextToken = "$" + nextToken.substring(nextToken.indexOf('{'), nextToken.indexOf('}') + 1); // NOI18N
            final String resolvedToken = AntParser.resolveProperties(doc, nextToken);
            builder.append(nextToken).append(" -> ").append(resolvedToken); // NOI18N
            if (tokenizer.hasMoreTokens()) {
              builder.append("<br>"); // NOI18N
            }
          }
        }
        builder.append("</p></html>"); // NOI18N
        retVal = builder.toString();
      }
    }
    return retVal;
  }
}
